# -*- coding:utf-8 -*-

from random import choice, random, expovariate

from usuario import Usuario


class Evento(object):
    """Classe abstrata que representa um evento.
    """

    def processar(self, simulador): 
        raise NotImplementedError()


class ChegadaExternaPeer(Evento):
    """Representa a chegada externa de um peer
    """

    def __repr__(self):
        return '[chegada_externa_peer]'
        
    def processar(self, simulador):
        """Gera um novo peer
           Agenda novo evento 
        """
        #cria peer
        peer = Usuario(arquivo=simulador.arquivo, classe='peer', nome='e%s' % Usuario.proximo_peer_externo, blocos=[])#[?] Não entendi o valor no parâmetro nome

        peer.chegada = simulador.tempo_agora
        peer.rodada_entrou = simulador.rodada
        simulador.debug_print('[externa] {0}'.format(peer))
        simulador.peers.append(peer)
        Usuario.proximo_peer_externo += 1

        #agenda próximo tique do relógio
        dt = expovariate(lambd=simulador.lambida)
        simulador.eventos.adicionar(simulador.tempo_agora + dt, ChegadaExternaPeer())


class Saida(Evento):
    """Representa a saida de um usuario
    """

    def __init__(self, usuario):
        self.usuario = usuario


    def __repr__(self):
        return '[saida] {0}'.format(self.usuario)

        
    def processar(self, simulador):
        """Retirar o seed do grupo de seeds
           Retirar da fila eventos que já haviam sido agendados
           Recomenda download
        """
        #retira seed 
        simulador.seeds.remove(self.usuario)
        self.usuario.saiu = simulador.tempo_agora
        self.usuario.rodada_saiu = simulador.rodada
        simulador.seeds_sairam.append(self.usuario)

        #retira eventos agendados
        for time, evento in simulador.eventos:

            #Se evento tem usuario
            if hasattr(evento, 'usuario'):

                if Usuario.equal(self.usuario, evento.usuario):
                    simulador.debug_print('delete_evento {0}'.format(evento))
                    simulador.eventos.remove((time, evento))

                    #só há um evento desse tipo na fila
                    """Porque o evento de saida é gerado assim que o peer se torna seed.
                       Qualquer evento de upload gerado pra esse seed é tratado antes de ser agendado o próximo upload.
                       Logo, no máximo há 1 evento de upload agendado para depois da saída.
                    """
                    break
 
        #recomendação
        if random() <= simulador.p: #[!]Corrigido!
            peer = Usuario(arquivo=simulador.arquivo, classe='peer', nome='r%s' % self.usuario.nome, blocos=[])
            peer.chegada = simulador.tempo_agora
            peer.rodada_entrou = simulador.rodada 
            simulador.debug_print('[recomendacao] {0} - {1}'.format(self.usuario, peer))
            simulador.peers.append(peer)
     

class Upload(Evento):
    """Representa a tentativa de upload de um usuário 
       Agenda novo evento 
    """

    def __init__(self, usuario):
        self.usuario = usuario

    def __repr__(self):
        return '[upload] {0}'.format(self.usuario)

        
    def processar(self, simulador):
        """Escolhe um peer. Caso esse peer queira algum bloco que usuario tem,
           é realizado upload do bloco (usuario -> peer)
        """
        peer = self.escolhe_peer(simulador.peers)

        if peer:
            blocos_possiveis = self.usuario.blocos_candidatos_upload(peer)

            if blocos_possiveis:
                bloco = choice(blocos_possiveis)

                #Há peer e bloco -> upload
                peer.blocos.append(bloco)
                peer.blocos_faltam.remove(bloco)
                peer.blocos_novos.append(bloco)

                simulador.debug_print("[upload] bloco {0} - {1} {2} para {3} {4}".format(bloco, self.usuario.classe, self.usuario.nome, peer.classe, peer.nome))

                peer_vai_sair_agora = False
                #peer virou seed?
                if not peer.blocos_faltam:
                    simulador.debug_print("[peer -> seed] {0}".format(peer))
                    simulador.peers.remove(peer)
                    peer.classe = 'seed'
                    peer.virou_seed = simulador.tempo_agora
                    simulador.seeds.append(peer)
                    
                    #Para gamma = -1, temos gamma infinito. Assim, o peer sai assim que vira seed.
                    if simulador.gamma == -1:
                        #Sai do sistema agora!!!
                        dt = 0
                        peer_vai_sair_agora = True
                    else:
                        dt = peer.proximo_evento(simulador.gamma)

                    simulador.eventos.adicionar(simulador.tempo_agora + dt, Saida(peer))
                
                #Peer recebeu o PRIMEIRO bloco? -> Ele pode fazer upload
                if not peer_vai_sair_agora and len(peer.blocos) == 1:
                    #agendando primeiro tique do peer
                    dt = peer.proximo_evento(simulador.mu)
                    simulador.eventos.adicionar(simulador.tempo_agora + dt, Upload(peer))
                 

        #agendando próximo tique do relógio
        taxa_upload = simulador.u if self.usuario.classe == 'publisher' else simulador.mu
        dt = self.usuario.proximo_evento(taxa_upload)
        simulador.eventos.adicionar(simulador.tempo_agora + dt, Upload(self.usuario))


    def escolhe_peer(self, peers):
        #Qtd de peers > 0
        if peers:
            #Usuário é o único peer existente no sistema
            if self.usuario.classe == 'peer' and len(peers) == 1:
                return None
    
            #Escolher peer que não seja self
            peer = choice(peers)
            while(self.usuario.classe == 'peer' and peer.nome == self.usuario.nome):
                peer = choice(peers)

            return peer
        
        return None
            

